package com.park;

import com.park.controller.Controller;
import com.park.controller.PageController;
import com.park.controller.RestController;
import org.apache.commons.lang.StringUtils;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.Path;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

import static java.util.Arrays.asList;
import static org.apache.commons.lang.StringUtils.substringAfter;

/**
 * @author Aaron
 * @date 2021/2/28 16:42
 */
public class DispatcherServlet extends HttpServlet {
	private static final Logger logger = Logger.getLogger(DispatcherServlet.class.getName());

	/**
	 * 保存路径与方法的映射
	 */
	private Map<String, HandlerMethodInfo> handleMethodInfoMapping = new HashMap<>();
	/**
	 * 保存路径与 Controller 的映射
	 */
	private Map<String, Controller> controllersMapping = new HashMap<>();
	/**
	 * object 方法名称集合
	 */
	private static Set<String> objectMethod = new HashSet<>();

	static {
		objectMethod.add("wait");
		objectMethod.add("equals");
		objectMethod.add("toString");
		objectMethod.add("hashCode");
		objectMethod.add("getClass");
		objectMethod.add("notify");
		objectMethod.add("notifyAll");
	}

	@Override
	protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		// 建立映射关系
		// requestURI = /a/hello/world
		String requestURI = request.getRequestURI();
		// contextPath  = /a or "/" or ""
		String servletContextPath = request.getContextPath();
		String prefixPath = servletContextPath;
		// 映射路径（子路径）
		String requestMappingPath = substringAfter(requestURI,
				StringUtils.replace(prefixPath, "//", "/"));
		// 映射到 Controller
		logger.info("requestMappingPath:" + requestMappingPath);
		Controller controller = controllersMapping.get(requestMappingPath);
		logger.info("controller:" + controller);
		if (controller != null) {

			HandlerMethodInfo handlerMethodInfo = handleMethodInfoMapping.get(requestMappingPath);
			logger.info("handlerMethodInfo:" + handlerMethodInfo);
			try {
				if (handlerMethodInfo != null) {

					String httpMethod = request.getMethod();

					if (!handlerMethodInfo.getSupportedHttpMethods().contains(httpMethod)) {
						// HTTP 方法不支持
						response.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
						return;
					}

					// 使用反射调用用户自定义的方法
					String viewPath = (String) handlerMethodInfo.getHandlerMethod().invoke(controller, request, response);
					logger.info("viewPath:" + viewPath);
					// 页面请求 forward
					// request -> RequestDispatcher forward
					// RequestDispatcher requestDispatcher = request.getRequestDispatcher(viewPath);
					// ServletContext -> RequestDispatcher forward
					// ServletContext -> RequestDispatcher 必须以 "/" 开头
					ServletContext servletContext = request.getServletContext();
					if (!viewPath.startsWith("/")) {
						viewPath = "/" + viewPath;
					}
					RequestDispatcher requestDispatcher = servletContext.getRequestDispatcher(viewPath);
					requestDispatcher.forward(request, response);
					return;

				}
			} catch (Throwable throwable) {
				if (throwable.getCause() instanceof IOException) {
					throw (IOException) throwable.getCause();
				} else {
					throw new ServletException(throwable.getCause());
				}
			}
		}
	}

	@Override
	public void init() throws ServletException {
		logger.log(Level.INFO, "初始化 DispatcherServlet!");
		initHandleMethods();
	}

	private void initHandleMethods() {
		// 通过 java spi 机制获取所有 Controller 接口的实现
		for (Controller controller : ServiceLoader.load(Controller.class)) {
			Class<?> controllerClass = controller.getClass();
			Path pathFromClass = controllerClass.getAnnotation(Path.class);
			String requestPath = pathFromClass.value();
			Method[] publicMethods = controllerClass.getMethods();
			// 处理方法支持的 HTTP 方法集合
			for (Method method : publicMethods) {
				Set<String> supportedHttpMethods = findSupportedHttpMethods(method);
				Path pathFromMethod = method.getAnnotation(Path.class);
				if (pathFromMethod != null) {
					requestPath += pathFromMethod.value();
				}
				// 将用户定义的 方法天啊及到 HandlerMethodInfo 中
				if (!isObjectMethod(method)) {
					handleMethodInfoMapping.put(requestPath, new HandlerMethodInfo(requestPath, method, supportedHttpMethods));
				}
			}
			controllersMapping.put(requestPath, controller);
		}
		logger.log(Level.INFO, "handleMethodInfoMapping:" + handleMethodInfoMapping);
		logger.log(Level.INFO, "controllersMapping:" + controllersMapping);
	}

	private Set<String> findSupportedHttpMethods(Method method) {
		Set<String> supportedHttpMethods = new LinkedHashSet<>();
		for (Annotation annotationFromMethod : method.getAnnotations()) {
			HttpMethod httpMethod = annotationFromMethod.annotationType().getAnnotation(HttpMethod.class);
			if (httpMethod != null) {
				supportedHttpMethods.add(httpMethod.value());
			}
		}

		if (supportedHttpMethods.isEmpty()) {
			supportedHttpMethods.addAll(asList(HttpMethod.GET, HttpMethod.POST, HttpMethod.PUT, HttpMethod.DELETE, HttpMethod.HEAD, HttpMethod.OPTIONS));
		}

		return supportedHttpMethods;
	}

	private boolean isObjectMethod(Method method) {
		return objectMethod.contains(method.getName());
	}

}
